In [51]:
# camera calibration
import numpy as np
import cv2
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import pickle
import glob

#read in all images
images = glob.glob('camera_cal/calibration*.jpg')


objpoints = []
imgpoints = []

objp = np.zeros((6*9,3), np.float32)
objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2)


for fname in images:
    #read in each image
    img = mpimg.imread(fname)
    gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
    ret, corners = cv2.findChessboardCorners(gray, (9,6), None)
    if ret == True:
        imgpoints.append(corners)
        objpoints.append(objp)
        #img = cv2.drawChessboardCorners(img, (9,6), corners, ret)
 
In [52]:
#load in one image to get the shape
img = mpimg.imread('camera_cal/calibration1.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY) #just to get the shape
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
In [53]:
#show one example

dst = cv2.undistort(img, mtx, dist, None, mtx)
%matplotlib inline
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(24, 9))
f.tight_layout()
ax1.imshow(img)
ax1.set_title('Original Image', fontsize=50)
ax2.imshow(dst)
ax2.set_title('Undistorted Image', fontsize=50)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)
In [54]:
#show another example
img2 = mpimg.imread('camera_cal/calibration2.jpg')
dst2 = cv2.undistort(img2, mtx, dist, None, mtx)
%matplotlib inline
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(24, 9))
f.tight_layout()
ax1.imshow(img2)
ax1.set_title('Original Image', fontsize=50)
ax2.imshow(dst2)
ax2.set_title('Undistorted Image', fontsize=50)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)
In [55]:
#save the calibration result 
import pickle
camera_calib_dict = {}
camera_calib_dict['mtx'] = mtx
camera_calib_dict['dist'] = dist
camera_calib_dict['rvecs'] = rvecs
camera_calib_dict['tvecs'] = tvecs
with open('calibration.p','wb') as f:
    pickle.dump(camera_calib_dict, f)
f.close()
    
In [203]:
# start from straight lanes 
test_image = mpimg.imread('test_images/straight_lines1.jpg')
dst_test = cv2.undistort(test_image, mtx, dist, None, mtx)
%matplotlib inline
f, (ax1,ax2) = plt.subplots(1, 2, figsize=(24, 9))
ax1.imshow(test_image)
ax2.imshow(dst_test)
Out[203]:
<matplotlib.image.AxesImage at 0x12c5b3390>
In [204]:
#thresholding pipeline to detect the edges
def abs_sobel_thresh(img, orient='x', sobel_kernel=3, thresh=(0, 255)):
    gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
    if orient == 'x':
        sobel = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    elif orient == 'y':
        sobel = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=sobel_kernel)
    abs_sobel = np.absolute(sobel)

    scaled_sobel = np.uint8(255*abs_sobel/np.max(abs_sobel))
    grad_binary = np.zeros_like(scaled_sobel)
    grad_binary[(scaled_sobel >= thresh[0]) & (scaled_sobel <= thresh[1])] = 1
    return grad_binary

def mag_threshold(img, sobel_kernel=3, mag_thresh=(0, 255)):
    gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)

    sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=sobel_kernel)

    sobelmag = np.sqrt(np.sum([np.square(sobelx), np.square(sobely)], axis=0 ))
    scaled_sobel = np.uint8(255*sobelmag/np.max(sobelmag))

    mag_binary = np.zeros_like(scaled_sobel)
    mag_binary[(scaled_sobel >= mag_thresh[0]) & (scaled_sobel <= mag_thresh[1])] = 1
    return mag_binary

def dir_threshold(img, sobel_kernel=3, thresh=(0, np.pi/2)):
    gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
    sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=sobel_kernel)
    sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=sobel_kernel)
    abs_sobelx = np.absolute(sobelx)
    abs_sobely = np.absolute(sobely)
    sobel_angle = np.arctan2(abs_sobely, abs_sobelx)
    dir_binary = np.zeros_like(sobel_angle)
    dir_binary[(sobel_angle >= thresh[0]) & (sobel_angle <= thresh[1])] = 1
    return dir_binary

def hls_select(img, thresh=(0, 255)):
    hls = cv2.cvtColor(img, cv2.COLOR_RGB2HLS)
    s_channel = hls[:,:,2]
    binary_output = np.zeros_like(s_channel)
    binary_output[(s_channel > thresh[0]) & (s_channel <= thresh[1])] = 1
    return binary_output

# Choose a Sobel kernel size
ksize = 3 # Choose a larger odd number to smooth gradient measurements

# Apply each of the thresholding functions
gradx = abs_sobel_thresh(dst_test, orient='x', sobel_kernel=ksize, thresh=(20, 100))
grady = abs_sobel_thresh(dst_test, orient='y', sobel_kernel=ksize, thresh=(20, 100))
gradient_binary = np.zeros_like(grady)
#combine absolute gradients
gradient_binary[(gradx == 1) & (grady == 1)] = 1
mag_binary = mag_threshold(dst_test, sobel_kernel=ksize, mag_thresh=(30, 100))
dir_binary = dir_threshold(dst_test, sobel_kernel=15, thresh=(0.7, 1.3))
#combine magnitude and directional threshold
mag_dir_binary = np.zeros_like(dir_binary)
mag_dir_binary[(mag_binary == 1) & (dir_binary == 1)] = 1
#hls colorspace
hls_binary = hls_select(dst_test, thresh=(190, 255))
%matplotlib inline
f, (ax1,ax2,ax3) = plt.subplots(1, 3, figsize=(24, 9))
#show the three threshold
ax1.imshow(gradient_binary, cmap='gray')     
ax2.imshow(mag_dir_binary,cmap='gray')    
ax3.imshow(hls_binary,cmap='gray')    
Out[204]:
<matplotlib.image.AxesImage at 0x134d38cc0>
In [205]:
combined_binary = np.zeros_like(hls_binary)
combined_binary[(gradient_binary == 1) | (mag_dir_binary == 1)|(hls_binary == 1)] = 1
%matplotlib inline
f, ax = plt.subplots(1, 1, figsize=(24, 9))
#show the resulting image combing three threshold
ax.imshow(combined_binary, cmap= 'gray')                    
Out[205]:
<matplotlib.image.AxesImage at 0x12f6839e8>
In [206]:
#perspective transform 
img_size =(combined_binary.shape[1], test_image.shape[0])
#TUNE the points until the lanes are parallel
src = np.float32([[600,450],[680,450], [1080, img_size[1]], [200, img_size[1]]])
src = src.reshape((-1,1,2))
#(600,450),(680,450),(1080,720), (200,720)
dst_test_overlay = cv2.line(dst_test,(600,450),(680,450),(0,0,255),3)
dst_test_overlay = cv2.line(dst_test,(680,450),(1080,img_size[1]),(0,0,255),3)
dst_test_overlay = cv2.line(dst_test,(600,450),(200,img_size[1]),(0,0,255),3)
dst = np.float32([[350,0],[1000,0],[1000, img_size[1]],[350, img_size[1]]])
M = cv2.getPerspectiveTransform(src,dst)
Minv = cv2.getPerspectiveTransform(dst, src)
dst_test_warped = cv2.warpPerspective(dst_test_overlay, M, img_size, flags=cv2.INTER_LINEAR)
%matplotlib inline
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(24, 9))
f.tight_layout()
ax1.imshow(dst_test_overlay)
ax1.set_title('Original Image', fontsize=50)
ax2.imshow(dst_test_warped)
ax2.set_title('Undistorted and Warped Image', fontsize=50)
plt.subplots_adjust(left=0., right=1, top=0.9, bottom=0.)
(1280, 720)
In [207]:
#apply the perspective transform on the binary image
binary_warped = cv2.warpPerspective(combined_binary, M, img_size, flags=cv2.INTER_LINEAR)
out_img = np.dstack((binary_warped, binary_warped, binary_warped))*255
%matplotlib inline
f, ax = plt.subplots(1, 1, figsize=(24, 9))
#show the resulting image combing three threshold
ax.imshow(out_img)        
Out[207]:
<matplotlib.image.AxesImage at 0x145efaf28>
In [208]:
#detect lanes

#apply on a test image
test_image2 = mpimg.imread('test_images/test2.jpg')
dst_test2 = cv2.undistort(test_image2, mtx, dist, None, mtx)
%matplotlib inline
f, (ax1,ax2) = plt.subplots(1, 2, figsize=(24, 9))
ax1.imshow(test_image2)
#print(test_image2.shape)
ax2.imshow(dst_test2)
Out[208]:
<matplotlib.image.AxesImage at 0x14d85c6d8>
In [209]:
# Apply each of the thresholding functions
gradx = abs_sobel_thresh(dst_test2, orient='x', sobel_kernel=ksize, thresh=(20, 100))
grady = abs_sobel_thresh(dst_test2, orient='y', sobel_kernel=ksize, thresh=(20, 100))
gradient_binary = np.zeros_like(grady)
#combine absolute gradients
gradient_binary[(gradx == 1) & (grady == 1)] = 1
mag_binary = mag_threshold(dst_test2, sobel_kernel=ksize, mag_thresh=(30, 100))
dir_binary = dir_threshold(dst_test2, sobel_kernel=15, thresh=(0.7, 1.3))
#combine magnitude and directional threshold
mag_dir_binary = np.zeros_like(dir_binary)
mag_dir_binary[(mag_binary == 1) & (dir_binary == 1)] = 1
#hls colorspace
hls_binary = hls_select(dst_test2, thresh=(190, 255))
%matplotlib inline
f, (ax1,ax2,ax3) = plt.subplots(1, 3, figsize=(24, 9))
#show the three threshold
ax1.imshow(gradient_binary, cmap='gray')     
ax2.imshow(mag_dir_binary,cmap='gray')    
ax3.imshow(hls_binary,cmap='gray')    
Out[209]:
<matplotlib.image.AxesImage at 0x13806ba20>
In [210]:
combined_binary = np.zeros_like(hls_binary)
combined_binary[(gradient_binary == 1) | (mag_dir_binary == 1)|(hls_binary == 1)] = 1
%matplotlib inline
f, ax = plt.subplots(1, 1, figsize=(24, 9))
#show the resulting image combing three threshold
ax.imshow(combined_binary, cmap= 'gray')                    
Out[210]:
<matplotlib.image.AxesImage at 0x146bc57b8>
In [211]:
#apply the perspective transform on the binary image
binary_warped = cv2.warpPerspective(combined_binary, M, img_size, flags=cv2.INTER_LINEAR)
out_img = np.dstack((binary_warped, binary_warped, binary_warped))*255
%matplotlib inline
f, ax = plt.subplots(1, 1, figsize=(24, 9))
#show the resulting image combing three threshold
ax.imshow(out_img)        
Out[211]:
<matplotlib.image.AxesImage at 0x146c1d128>
In [212]:
print(binary_warped.shape)

histogram = np.sum(binary_warped[binary_warped.shape[0]//2:,:], axis=0)
plt.plot(histogram)
(720, 1280)
Out[212]:
[<matplotlib.lines.Line2D at 0x1400546d8>]
In [213]:
midpoint = np.int(histogram.shape[0]/2)
#Peak on the left
leftx_base = np.argmax(histogram[:midpoint])
print(leftx_base)
#peak on the right
rightx_base = np.argmax(histogram[midpoint:]) + midpoint
print(rightx_base)
406
1067
In [214]:
#detect lanes for the image
nwindows = 9
window_height = np.int(binary_warped.shape[0]/nwindows)
# Identify the x and y positions of all nonzero pixels in the image
nonzero = binary_warped.nonzero()
nonzeroy = np.array(nonzero[0])
nonzerox = np.array(nonzero[1])
# Current positions to be updated for each window
leftx_current = leftx_base
rightx_current = rightx_base
# Set the width of the windows +/- margin
margin = 100
# Set minimum number of pixels found to recenter window
minpix = 50
# Create empty lists to receive left and right lane pixel indices
left_lane_inds = []
right_lane_inds = []

# Step through the windows one by one
for window in range(nwindows):
    # Identify window boundaries in x and y (and right and left)
    win_y_low = binary_warped.shape[0] - (window+1)*window_height
    win_y_high = binary_warped.shape[0] - window*window_height
    win_xleft_low = leftx_current - margin
    win_xleft_high = leftx_current + margin
    win_xright_low = rightx_current - margin
    win_xright_high = rightx_current + margin
    # Draw the windows on the visualization image
    cv2.rectangle(out_img,(win_xleft_low,win_y_low),(win_xleft_high,win_y_high),
    (0,255,0), 2) 
    cv2.rectangle(out_img,(win_xright_low,win_y_low),(win_xright_high,win_y_high),
    (0,255,0), 2) 
    # Identify the nonzero pixels in x and y within the window
    good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) & 
    (nonzerox >= win_xleft_low) &  (nonzerox < win_xleft_high)).nonzero()[0]
    good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) & 
    (nonzerox >= win_xright_low) &  (nonzerox < win_xright_high)).nonzero()[0]
    # Append these indices to the lists
    left_lane_inds.append(good_left_inds)
    right_lane_inds.append(good_right_inds)
    # If you found > minpix pixels, recenter next window on their mean position
    if len(good_left_inds) > minpix:
        leftx_current = np.int(np.mean(nonzerox[good_left_inds]))
    if len(good_right_inds) > minpix:        
        rightx_current = np.int(np.mean(nonzerox[good_right_inds]))

# Concatenate the arrays of indices
left_lane_inds = np.concatenate(left_lane_inds)
right_lane_inds = np.concatenate(right_lane_inds)

# Extract left and right line pixel positions
leftx = nonzerox[left_lane_inds]
lefty = nonzeroy[left_lane_inds] 
rightx = nonzerox[right_lane_inds]
righty = nonzeroy[right_lane_inds] 

# Fit a second order polynomial to each
left_fit = np.polyfit(lefty, leftx, 2)
right_fit = np.polyfit(righty, rightx, 2)


# Generate x and y values for plotting
ploty = np.linspace(0, binary_warped.shape[0]-1, binary_warped.shape[0] )
left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]

out_img[nonzeroy[left_lane_inds], nonzerox[left_lane_inds]] = [255, 0, 0]
out_img[nonzeroy[right_lane_inds], nonzerox[right_lane_inds]] = [0, 0, 255]
plt.imshow(out_img)
plt.plot(left_fitx, ploty, color='yellow')
plt.plot(right_fitx, ploty, color='yellow')
plt.xlim(0, 1280)
plt.ylim(720, 0)
Out[214]:
(720, 0)
In [215]:
# project back onto the original image
# Create an image to draw the lines on
warp_zero = np.zeros_like(binary_warped).astype(np.uint8)
color_warp = np.dstack((warp_zero, warp_zero, warp_zero))

# Recast the x and y points into usable format for cv2.fillPoly()
pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
pts = np.hstack((pts_left, pts_right))

# Draw the lane onto the warped blank image
cv2.fillPoly(color_warp, np.int_([pts]), (0,255, 0))

# Warp the blank back to original image space using inverse perspective matrix (Minv)
newwarp = cv2.warpPerspective(color_warp, Minv, (test_image2.shape[1], test_image2.shape[0])) 
# Combine the result with the original image
result = cv2.addWeighted(dst_test2, 1, newwarp, 0.3, 0)
plt.imshow(result)
Out[215]:
<matplotlib.image.AxesImage at 0x1380142b0>
In [216]:
#calculate curvature

y_eval = np.max(ploty)
left_curverad = ((1 + (2*left_fit[0]*y_eval + left_fit[1])**2)**1.5) / np.absolute(2*left_fit[0])
right_curverad = ((1 + (2*right_fit[0]*y_eval + right_fit[1])**2)**1.5) / np.absolute(2*right_fit[0])
print(left_curverad,'pixel',right_curverad,'pixel')


#pixel to real world
# Define conversions in x and y from pixels space to meters
ym_per_pix = 30/720 # meters per pixel in y dimension
xm_per_pix = 3.7/700 # meters per pixel in x dimension

# Fit new polynomials to x,y in world space
left_fit_cr = np.polyfit(ploty*ym_per_pix, left_fitx*xm_per_pix, 2)
right_fit_cr = np.polyfit(ploty*ym_per_pix, right_fitx*xm_per_pix, 2)
# Calculate the new radii of curvature
left_curverad = ((1 + (2*left_fit_cr[0]*y_eval*ym_per_pix + left_fit_cr[1])**2)**1.5) / np.absolute(2*left_fit_cr[0])
right_curverad = ((1 + (2*right_fit_cr[0]*y_eval*ym_per_pix + right_fit_cr[1])**2)**1.5) / np.absolute(2*right_fit_cr[0])
# Now our radius of curvature is in meters
print(left_curverad, 'm', right_curverad, 'm')
# Example values: 632.1 m    626.2 m
1166.4525542712677 pixel 1019.1964730613497 pixel
380.0966781973619 m 331.9094007189519 m
In [15]:
#initialize line class to track
class Line():
    def __init__(self):
        # was the line detected in the last iteration?
        self.detected = False  
        # x values of the last n fits of the line
        self.recent_xfitted = [] 
        #average x values of the fitted line over the last n iterations
        self.bestx = None     
        #polynomial coefficients averaged over the last n iterations
        self.best_fit = None  
        #polynomial coefficients for the most recent fit
        self.current_fit = [np.array([False])]  
        #radius of curvature of the line in some units
        self.radius_of_curvature = None 
        #distance in meters of vehicle center from the line
        self.line_base_pos = None 
        #difference in fit coefficients between last and new fits
        self.diffs = np.array([0,0,0], dtype='float') 
        #x values for detected line pixels
        self.allx = None  
        #y values for detected line pixels
        self.ally = None
In [287]:
#define pipeline
def calibrate_camera():
    #read in all images
    images = glob.glob('camera_cal/calibration*.jpg')
    objpoints = []
    imgpoints = []
    objp = np.zeros((6*9,3), np.float32)
    objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2)
    for fname in images:
        #read in each image
        img = mpimg.imread(fname)
        gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
        ret, corners = cv2.findChessboardCorners(gray, (9,6), None)
        if ret == True:
            imgpoints.append(corners)
            objpoints.append(objp)
    ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
    return ret, mtx, dist, rvecs, tvecs

def calculate_perspective_transform():
    #TUNE the points until the lanes are parallel
    src = np.float32([[600,450],[680,450], [1080, img_size[1]], [200, img_size[1]]])
    src = src.reshape((-1,1,2))
    dst = np.float32([[350,0],[1000,0],[1000, img_size[1]],[350, img_size[1]]])
    M = cv2.getPerspectiveTransform(src,dst)
    Minv = cv2.getPerspectiveTransform(dst, src)
    return M, Minv

def undistort(img, mtx, dist):
    undistorted_image = cv2.undistort(img, mtx, dist, None, mtx)
    return undistorted_image

def apply_threshold(img, ksize, thresh, mag_thresh, sobel_kernel, angle_thesh, hls_thresh):
    gradx = abs_sobel_thresh(img, orient='x', sobel_kernel=ksize, thresh=(20, 100))
    grady = abs_sobel_thresh(img, orient='y', sobel_kernel=ksize, thresh=(20, 100))
    gradient_binary = np.zeros_like(grady)
    #combine absolute gradients
    gradient_binary[(gradx == 1) & (grady == 1)] = 1
    mag_binary = mag_threshold(img, sobel_kernel=ksize, mag_thresh=mag_thresh)
    dir_binary = dir_threshold(img, sobel_kernel=sobel_kernel, thresh=angle_thesh)
    #combine magnitude and directional threshold
    mag_dir_binary = np.zeros_like(dir_binary)
    mag_dir_binary[(mag_binary == 1) & (dir_binary == 1)] = 1
    #hls colorspace
    hls_binary = hls_select(img, thresh=hls_thresh)
    combined_binary = np.zeros_like(hls_binary)
    combined_binary[(gradient_binary == 1) | (mag_dir_binary == 1)|(hls_binary == 1)] = 1
    return combined_binary

def apply_perspective_transform(img, M):
    img_size = (img.shape[1],img.shape[0])
    binary_warped = cv2.warpPerspective(img, M, img_size, flags=cv2.INTER_LINEAR)
    return binary_warped

def fit_lane(binary_warped):
    out_img = np.dstack((binary_warped, binary_warped, binary_warped))*255
    histogram = np.sum(binary_warped[binary_warped.shape[0]//2:,:], axis=0)
    midpoint = np.int(histogram.shape[0]/2)
    #Peak on the left
    leftx_base = np.argmax(histogram[:midpoint])
    #peak on the right
    rightx_base = np.argmax(histogram[midpoint:]) + midpoint

    nwindows = 9
    window_height = np.int(binary_warped.shape[0]/nwindows)

    # Identify the x and y positions of all nonzero pixels in the image
    nonzero = binary_warped.nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    # Current positions to be updated for each window
    leftx_current = leftx_base
    rightx_current = rightx_base
    # Set the width of the windows +/- margin
    margin = 100
    # Set minimum number of pixels found to recenter window
    minpix = 50
    # Create empty lists to receive left and right lane pixel indices
    left_lane_inds = []
    right_lane_inds = []

    # Step through the windows one by one
    for window in range(nwindows):
        # Identify window boundaries in x and y (and right and left)
        win_y_low = binary_warped.shape[0] - (window+1)*window_height
        win_y_high = binary_warped.shape[0] - window*window_height
        win_xleft_low = leftx_current - margin
        win_xleft_high = leftx_current + margin
        win_xright_low = rightx_current - margin
        win_xright_high = rightx_current + margin
        # Draw the windows on the visualization image
        cv2.rectangle(out_img,(win_xleft_low,win_y_low),(win_xleft_high,win_y_high),
        (0,255,0), 2) 
        cv2.rectangle(out_img,(win_xright_low,win_y_low),(win_xright_high,win_y_high),
        (0,255,0), 2) 
        # Identify the nonzero pixels in x and y within the window
        good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) & 
        (nonzerox >= win_xleft_low) &  (nonzerox < win_xleft_high)).nonzero()[0]
        good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) & 
        (nonzerox >= win_xright_low) &  (nonzerox < win_xright_high)).nonzero()[0]
        # Append these indices to the lists
        left_lane_inds.append(good_left_inds)
        right_lane_inds.append(good_right_inds)
        # If you found > minpix pixels, recenter next window on their mean position
        if len(good_left_inds) > minpix:
            leftx_current = np.int(np.mean(nonzerox[good_left_inds]))
        if len(good_right_inds) > minpix:        
            rightx_current = np.int(np.mean(nonzerox[good_right_inds]))

    # Concatenate the arrays of indices
    left_lane_inds = np.concatenate(left_lane_inds)
    right_lane_inds = np.concatenate(right_lane_inds)

    # Extract left and right line pixel positions
    leftx = nonzerox[left_lane_inds]
    lefty = nonzeroy[left_lane_inds] 
    rightx = nonzerox[right_lane_inds]
    righty = nonzeroy[right_lane_inds] 

    # Fit a second order polynomial to each
    left_fit = np.polyfit(lefty, leftx, 2)
    right_fit = np.polyfit(righty, rightx, 2)
    # Generate x and y values for plotting
    ploty = np.linspace(0, binary_warped.shape[0]-1, binary_warped.shape[0] )
    left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
    right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]
    out_img[nonzeroy[left_lane_inds], nonzerox[left_lane_inds]] = [255, 0, 0]
    out_img[nonzeroy[right_lane_inds], nonzerox[right_lane_inds]] = [0, 0, 255]
    return out_img, leftx, lefty, rightx, righty, ploty, left_fitx, right_fitx

def find_lane_next_frame(binary_warped):
    nonzero = binary_warped.nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    margin = 100
    left_lane_inds = ((nonzerox > (left_fit[0]*(nonzeroy**2) + left_fit[1]*nonzeroy + 
    left_fit[2] - margin)) & (nonzerox < (left_fit[0]*(nonzeroy**2) + 
    left_fit[1]*nonzeroy + left_fit[2] + margin))) 

    right_lane_inds = ((nonzerox > (right_fit[0]*(nonzeroy**2) + right_fit[1]*nonzeroy + 
    right_fit[2] - margin)) & (nonzerox < (right_fit[0]*(nonzeroy**2) + 
    right_fit[1]*nonzeroy + right_fit[2] + margin)))  

    # Again, extract left and right line pixel positions
    leftx = nonzerox[left_lane_inds]
    lefty = nonzeroy[left_lane_inds] 
    rightx = nonzerox[right_lane_inds]
    righty = nonzeroy[right_lane_inds]
    # Fit a second order polynomial to each
    left_fit = np.polyfit(lefty, leftx, 2)
    right_fit = np.polyfit(righty, rightx, 2)
    # Generate x and y values for plotting
    ploty = np.linspace(0, binary_warped.shape[0]-1, binary_warped.shape[0] )
    left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]
    right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]
    return leftx, lefty, rightx, righty, ploty, left_fitx, right_fitx
    
def draw_zone(binary_warped, original_img, undistorted_img, eft_fitx, right_fitx, ploty, Minv):
    warp_zero = np.zeros_like(binary_warped).astype(np.uint8)
    color_warp = np.dstack((warp_zero, warp_zero, warp_zero))

    # Recast the x and y points into usable format for cv2.fillPoly()
    pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
    pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
    pts = np.hstack((pts_left, pts_right))
    # Draw the lane onto the warped blank image
    cv2.fillPoly(color_warp, np.int_([pts]), (0,255, 0))
    # Warp the blank back to original image space using inverse perspective matrix (Minv)
    newwarp = cv2.warpPerspective(color_warp, Minv, (original_img.shape[1], original_img.shape[0])) 
    # Combine the result with the original image
    result = cv2.addWeighted(undistorted_img, 1, newwarp, 0.3, 0)
    return result

def calculate_curvature(ploty, left_fitx, right_fitx):
#     y_eval = np.max(ploty)
#     left_curverad = ((1 + (2*left_fit[0]*y_eval + left_fit[1])**2)**1.5) / np.absolute(2*left_fit[0])
#     right_curverad = ((1 + (2*right_fit[0]*y_eval + right_fit[1])**2)**1.5) / np.absolute(2*right_fit[0])
#     print(left_curverad,'pixel',right_curverad,'pixel')

    #pixel to real world
    # Define conversions in x and y from pixels space to meters
    ym_per_pix = 30/720 # meters per pixel in y dimension
    xm_per_pix = 3.7/700 # meters per pixel in x dimension

    # Fit new polynomials to x,y in world space
    left_fit_cr = np.polyfit(ploty*ym_per_pix, left_fitx*xm_per_pix, 2)
    right_fit_cr = np.polyfit(ploty*ym_per_pix, right_fitx*xm_per_pix, 2)
    # Calculate the new radii of curvature
    left_curverad = ((1 + (2*left_fit_cr[0]*y_eval*ym_per_pix + left_fit_cr[1])**2)**1.5) / np.absolute(2*left_fit_cr[0])
    right_curverad = ((1 + (2*right_fit_cr[0]*y_eval*ym_per_pix + right_fit_cr[1])**2)**1.5) / np.absolute(2*right_fit_cr[0])
    # Now our radius of curvature is in meters
    return left_curverad, right_curverad

def detect_lane(img, mtx, dist,ksize, thresh, mag_thresh, sobel_kernel, angle_thesh, hls_thresh, M, Minv):
    undistorted_image = undistort(img, mtx, dist)
    binary_image = apply_threshold(undistorted_image, ksize, thresh, mag_thresh, sobel_kernel, angle_thesh, hls_thresh)
    binary_image_warped = apply_perspective_transform(binary_image, M)
    out_img, leftx, lefty, rightx, righty, ploty, left_fitx, right_fitx = fit_lane(binary_image_warped)
    result = draw_zone(binary_image_warped,img,undistorted_image, left_fitx, right_fitx, ploty, Minv)
    left_curverad, right_curverad = calculate_curvature(ploty, left_fitx, right_fitx)
    cv2.putText(result, "left radius:"+ str(int(left_curverad))+'m', (20, 60), cv2.FONT_HERSHEY_SIMPLEX, 2, (255,255,255))
    cv2.putText(result, "right radius:"+ str(int(right_curverad))+'m', (20, 160), cv2.FONT_HERSHEY_SIMPLEX, 2, (255,255,255))
    return result

def detect_lane_by_default(img):
    ret, mtx, dist, rvecs, tvecs = calibrate_camera()
    M, Minv = calculate_perspective_transform()
    mag_thresh=(30, 100)
    angle_thesh=(0.7, 1.3)
    hls_thresh=(190, 255)
    sobel_kernel = 15
    ksize =3 
    undistorted_image = undistort(img, mtx, dist)
    binary_image = apply_threshold(undistorted_image, ksize, thresh, mag_thresh, sobel_kernel, angle_thesh, hls_thresh)
    binary_image_warped = apply_perspective_transform(binary_image, M)
    out_img, leftx, lefty, rightx, righty, ploty, left_fitx, right_fitx = fit_lane(binary_image_warped)
    result = draw_zone(binary_image_warped,img,undistorted_image, left_fitx, right_fitx, ploty, Minv)
    left_curverad, right_curverad = calculate_curvature(ploty, left_fitx, right_fitx)
    cv2.putText(result, "left radius:"+ str(int(left_curverad))+'m', (20, 60), cv2.FONT_HERSHEY_SIMPLEX, 2, (255,255,255))
    cv2.putText(result, "right radius:"+ str(int(right_curverad))+'m', (20, 160), cv2.FONT_HERSHEY_SIMPLEX, 2, (255,255,255))
    return result
In [288]:
#input 
thresh=(20, 100)
mag_thresh=(30, 100)
angle_thesh=(0.7, 1.3)
hls_thresh=(190, 255)
sobel_kernel = 15
ksize =3 
ret, mtx, dist, rvecs, tvecs = calibrate_camera()
M, Minv = calculate_perspective_transform()
In [289]:
#apply to different images
img = mpimg.imread('test_images/test2.jpg')
result = detect_lane(img, mtx, dist, ksize, thresh, mag_thresh, sobel_kernel, angle_thesh, hls_thresh, M, Minv)
%matplotlib inline
f, (ax1,ax2) = plt.subplots(1, 2, figsize=(24, 9))
ax1.imshow(img)
ax2.imshow(result)
Out[289]:
<matplotlib.image.AxesImage at 0x15ba0d588>
In [284]:
left_lane = Line()
right_lane = Line()
print(left_lane.line_base_pos)
None
In [290]:
from moviepy.editor import VideoFileClip
from IPython.display import HTML

clip = VideoFileClip("project_video.mp4").subclip(0,5)
clip_annotated = clip.fl_image(detect_lane_by_default)
%time clip_annotated.write_videofile("project_video_out.mp4", audio=False)



# vidcap = cv2.VideoCapture('big_buck_bunny_720p_5mb.mp4')
# success,image = vidcap.read()
# count = 0
# success = True
# while success:
#   success,image = vidcap.read()
#   print('Read a new frame: ', success)
#   cv2.imwrite("frame%d.jpg" % count, image)     # save frame as JPEG file
#   count += 1
[MoviePy] >>>> Building video project_video_out.mp4
[MoviePy] Writing video project_video_out.mp4
  0%|          | 0/126 [00:00<?, ?it/s]
  1%|          | 1/126 [00:05<12:26,  5.97s/it]
  2%|▏         | 2/126 [00:11<12:15,  5.93s/it]
  2%|▏         | 3/126 [00:17<12:05,  5.90s/it]
  3%|▎         | 4/126 [00:23<12:03,  5.93s/it]
  4%|▍         | 5/126 [00:29<11:54,  5.91s/it]
  5%|▍         | 6/126 [00:35<11:56,  5.97s/it]
  6%|▌         | 7/126 [00:41<11:48,  5.95s/it]
  6%|▋         | 8/126 [00:47<11:40,  5.94s/it]
  7%|▋         | 9/126 [00:53<11:33,  5.93s/it]
  8%|▊         | 10/126 [00:59<11:28,  5.93s/it]
  9%|▊         | 11/126 [01:06<11:51,  6.18s/it]
 10%|▉         | 12/126 [01:12<11:43,  6.17s/it]
 10%|█         | 13/126 [01:18<11:38,  6.18s/it]
 11%|█         | 14/126 [01:24<11:23,  6.10s/it]
 12%|█▏        | 15/126 [01:30<11:09,  6.03s/it]
 13%|█▎        | 16/126 [01:35<10:54,  5.95s/it]
 13%|█▎        | 17/126 [01:41<10:42,  5.90s/it]
 14%|█▍        | 18/126 [01:47<10:32,  5.85s/it]
 15%|█▌        | 19/126 [01:53<10:24,  5.83s/it]
 16%|█▌        | 20/126 [01:59<10:27,  5.92s/it]
 17%|█▋        | 21/126 [02:05<10:29,  5.99s/it]
 17%|█▋        | 22/126 [02:11<10:22,  5.99s/it]
 18%|█▊        | 23/126 [02:17<10:16,  5.98s/it]
 19%|█▉        | 24/126 [02:23<10:10,  5.98s/it]
 20%|█▉        | 25/126 [02:29<09:58,  5.93s/it]
 21%|██        | 26/126 [02:35<09:53,  5.94s/it]
 21%|██▏       | 27/126 [02:41<09:54,  6.00s/it]
 22%|██▏       | 28/126 [02:47<09:54,  6.07s/it]
 23%|██▎       | 29/126 [02:53<09:44,  6.03s/it]
 24%|██▍       | 30/126 [02:59<09:40,  6.04s/it]
 25%|██▍       | 31/126 [03:05<09:34,  6.05s/it]
 25%|██▌       | 32/126 [03:11<09:25,  6.02s/it]
 26%|██▌       | 33/126 [03:17<09:17,  6.00s/it]
 27%|██▋       | 34/126 [03:23<09:09,  5.97s/it]
 28%|██▊       | 35/126 [03:29<09:01,  5.95s/it]
 29%|██▊       | 36/126 [03:35<08:55,  5.95s/it]
 29%|██▉       | 37/126 [03:41<08:51,  5.97s/it]
 30%|███       | 38/126 [03:47<08:45,  5.97s/it]
 31%|███       | 39/126 [03:53<08:43,  6.01s/it]
 32%|███▏      | 40/126 [03:59<08:35,  6.00s/it]
 33%|███▎      | 41/126 [04:05<08:30,  6.00s/it]
 33%|███▎      | 42/126 [04:11<08:21,  5.98s/it]
 34%|███▍      | 43/126 [04:17<08:16,  5.99s/it]
 35%|███▍      | 44/126 [04:23<08:12,  6.01s/it]
 36%|███▌      | 45/126 [04:29<08:03,  5.97s/it]
 37%|███▋      | 46/126 [04:35<07:56,  5.95s/it]
 37%|███▋      | 47/126 [04:41<07:55,  6.02s/it]
 38%|███▊      | 48/126 [04:47<07:49,  6.02s/it]
 39%|███▉      | 49/126 [04:53<07:41,  5.99s/it]
 40%|███▉      | 50/126 [04:59<07:33,  5.97s/it]
 40%|████      | 51/126 [05:05<07:26,  5.95s/it]
 41%|████▏     | 52/126 [05:11<07:19,  5.94s/it]
 42%|████▏     | 53/126 [05:16<07:12,  5.92s/it]
 43%|████▎     | 54/126 [05:22<07:07,  5.94s/it]
 44%|████▎     | 55/126 [05:28<07:01,  5.94s/it]
 44%|████▍     | 56/126 [05:34<06:51,  5.88s/it]
 45%|████▌     | 57/126 [05:40<06:42,  5.83s/it]
 46%|████▌     | 58/126 [05:46<06:36,  5.82s/it]
 47%|████▋     | 59/126 [05:52<06:35,  5.91s/it]
 48%|████▊     | 60/126 [05:58<06:28,  5.88s/it]
 48%|████▊     | 61/126 [06:03<06:20,  5.86s/it]
 49%|████▉     | 62/126 [06:09<06:12,  5.81s/it]
 50%|█████     | 63/126 [06:15<06:04,  5.78s/it]
 51%|█████     | 64/126 [06:21<05:59,  5.79s/it]
 52%|█████▏    | 65/126 [06:27<05:56,  5.85s/it]
 52%|█████▏    | 66/126 [06:33<05:53,  5.89s/it]
 53%|█████▎    | 67/126 [06:38<05:46,  5.88s/it]
 54%|█████▍    | 68/126 [06:44<05:41,  5.89s/it]
 55%|█████▍    | 69/126 [06:50<05:35,  5.89s/it]
 56%|█████▌    | 70/126 [06:56<05:29,  5.88s/it]
 56%|█████▋    | 71/126 [07:02<05:23,  5.88s/it]
 57%|█████▋    | 72/126 [07:08<05:16,  5.86s/it]
 58%|█████▊    | 73/126 [07:14<05:09,  5.84s/it]
 59%|█████▊    | 74/126 [07:19<05:04,  5.86s/it]
 60%|█████▉    | 75/126 [07:25<05:00,  5.89s/it]
 60%|██████    | 76/126 [07:31<04:56,  5.94s/it]
 61%|██████    | 77/126 [07:37<04:51,  5.96s/it]
 62%|██████▏   | 78/126 [07:43<04:46,  5.97s/it]
 63%|██████▎   | 79/126 [07:49<04:39,  5.94s/it]
 63%|██████▎   | 80/126 [07:55<04:34,  5.96s/it]
 64%|██████▍   | 81/126 [08:01<04:27,  5.94s/it]
 65%|██████▌   | 82/126 [08:07<04:20,  5.92s/it]
 66%|██████▌   | 83/126 [08:13<04:13,  5.91s/it]
 67%|██████▋   | 84/126 [08:19<04:07,  5.89s/it]
 67%|██████▋   | 85/126 [08:25<04:02,  5.90s/it]
 68%|██████▊   | 86/126 [08:31<03:56,  5.91s/it]
 69%|██████▉   | 87/126 [08:37<03:50,  5.91s/it]
 70%|██████▉   | 88/126 [08:42<03:44,  5.90s/it]
 71%|███████   | 89/126 [08:48<03:38,  5.90s/it]
 71%|███████▏  | 90/126 [08:54<03:34,  5.95s/it]
 72%|███████▏  | 91/126 [09:00<03:29,  5.98s/it]
 73%|███████▎  | 92/126 [09:06<03:23,  5.97s/it]
 74%|███████▍  | 93/126 [09:12<03:16,  5.97s/it]
 75%|███████▍  | 94/126 [09:18<03:10,  5.95s/it]
 75%|███████▌  | 95/126 [09:24<03:05,  5.98s/it]
 76%|███████▌  | 96/126 [09:30<02:59,  5.99s/it]
 77%|███████▋  | 97/126 [09:36<02:52,  5.96s/it]
 78%|███████▊  | 98/126 [09:42<02:48,  6.01s/it]
 79%|███████▊  | 99/126 [09:48<02:42,  6.03s/it]
 79%|███████▉  | 100/126 [09:54<02:36,  6.00s/it]
 80%|████████  | 101/126 [10:00<02:29,  5.97s/it]
 81%|████████  | 102/126 [10:06<02:21,  5.91s/it]
 82%|████████▏ | 103/126 [10:12<02:15,  5.88s/it]
 83%|████████▎ | 104/126 [10:18<02:10,  5.92s/it]
 83%|████████▎ | 105/126 [10:24<02:05,  5.96s/it]
 84%|████████▍ | 106/126 [10:30<01:59,  5.98s/it]
 85%|████████▍ | 107/126 [10:36<01:54,  6.05s/it]
 86%|████████▌ | 108/126 [10:42<01:47,  6.00s/it]
 87%|████████▋ | 109/126 [10:48<01:41,  5.95s/it]
 87%|████████▋ | 110/126 [10:54<01:34,  5.92s/it]
 88%|████████▊ | 111/126 [11:00<01:28,  5.90s/it]
 89%|████████▉ | 112/126 [11:06<01:23,  5.97s/it]
 90%|████████▉ | 113/126 [11:12<01:18,  6.02s/it]
 90%|█████████ | 114/126 [11:18<01:12,  6.02s/it]
 91%|█████████▏| 115/126 [11:24<01:06,  6.05s/it]
 92%|█████████▏| 116/126 [11:30<01:00,  6.01s/it]
 93%|█████████▎| 117/126 [11:36<00:53,  6.00s/it]
 94%|█████████▎| 118/126 [11:42<00:47,  6.00s/it]
 94%|█████████▍| 119/126 [11:48<00:42,  6.06s/it]
 95%|█████████▌| 120/126 [11:54<00:36,  6.04s/it]
 96%|█████████▌| 121/126 [12:00<00:30,  6.06s/it]
 97%|█████████▋| 122/126 [12:06<00:24,  6.05s/it]
 98%|█████████▊| 123/126 [12:12<00:17,  6.00s/it]
 98%|█████████▊| 124/126 [12:18<00:11,  5.98s/it]
 99%|█████████▉| 125/126 [12:24<00:05,  5.96s/it]
[MoviePy] Done.
[MoviePy] >>>> Video ready: project_video_out.mp4 

CPU times: user 12min 45s, sys: 24 s, total: 13min 9s
Wall time: 12min 25s
In [292]:
HTML("""
<video width="960" height="540" controls>
  <source src="{0}">
</video>
""".format("project_video_out.mp4"))
Out[292]:
In [294]:
#complete videoclip
clip = VideoFileClip("project_video.mp4")
clip_annotated = clip.fl_image(detect_lane_by_default)
%time clip_annotated.write_videofile("project_video_out_complete.mp4", audio=False)
[MoviePy] >>>> Building video project_video_out_complete.mp4
[MoviePy] Writing video project_video_out_complete.mp4
  0%|          | 0/1261 [00:00<?, ?it/s]
100%|█████████▉| 1260/1261 [2:05:58<00:05,  5.95s/it] 
[MoviePy] Done.
[MoviePy] >>>> Video ready: project_video_out_complete.mp4 

CPU times: user 2h 9min 27s, sys: 4min, total: 2h 13min 27s
Wall time: 2h 5min 59s
In [295]:
HTML("""
<video width="960" height="540" controls>
  <source src="{0}">
</video>
""".format("project_video_out_complete.mp4"))
Out[295]: